package org.tulkas.selector.pattern;

import org.tulkas.selector.Filter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class Combinator implements Filter {
	private final Filter linked;

	public Combinator(Filter linked) {
		if (linked == null) {
			throw new NullPointerException(
					"Linked Filter of a Combinator can't be null!");
		}
		this.linked = linked;

	}

	public Filter getLinked() {
		return linked;
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(getDisplayName()).append(": (").append(linked.toString())
				.append(")");

		return sb.toString();
	}

	protected abstract String getDisplayName();

	public static class Ancestor extends Combinator {
		public Ancestor(Filter linked) {
			super(linked);
		}

		@Override
		public int getPriority() {
			return PRIORITY_COMBINATOR + 10;
		}

		@Override
		public boolean matches(Element element) {
			Node node = null;
			do {
				node = element.getParentNode();
				if ((node instanceof Element)
						&& getLinked().matches((Element) node)) {
					return true;
				}
			} while (node != null);
			return false;
		}

		@Override
		protected String getDisplayName() {
			return "ancestor";
		}
	}

	public static class Parent extends Combinator {
		public Parent(Filter linked) {
			super(linked);
		}

		@Override
		public int getPriority() {
			return PRIORITY_COMBINATOR;
		}

		@Override
		public boolean matches(Element element) {
			Node node = element.getParentNode();
			if ((node instanceof Element)
					&& getLinked().matches((Element) node)) {
				return true;
			} else {
				return false;
			}
		}

		@Override
		protected String getDisplayName() {
			return "parent";
		}
	}

	public static class AdjacentSibling extends Combinator {
		public AdjacentSibling(Filter linked) {
			super(linked);
		}

		@Override
		public int getPriority() {
			return PRIORITY_COMBINATOR;
		}

		@Override
		public boolean matches(Element element) {
			Node node = element.getPreviousSibling();
			if ((node instanceof Element)
					&& getLinked().matches((Element) node)) {
				return true;
			} else {
				return false;
			}
		}

		@Override
		protected String getDisplayName() {
			return "adjecent sibling";
		}
	}

	public static class Sibling extends Combinator {
		public Sibling(Filter linked) {
			super(linked);
		}

		@Override
		public int getPriority() {
			return PRIORITY_COMBINATOR + 20;
		}

		@Override
		public boolean matches(Element element) {
			Node node = null;
			do {
				node = element.getPreviousSibling();
				if ((node instanceof Element)
						&& getLinked().matches((Element) node)) {
					return true;
				}
			} while (node != null);
			return false;
		}

		@Override
		protected String getDisplayName() {
			return "sibling";
		}
	}
}
